/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor.loader.cglib;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.apache.ibatis.executor.loader.*;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.property.PropertyCopier;
import org.apache.ibatis.reflection.property.PropertyNamer;
import org.apache.ibatis.session.Configuration;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * @author Clinton Begin
 */
public class CglibProxyFactory implements ProxyFactory {
    /**
     * 在垃圾收集器将对象从内存中清除出去之前调用的方法
     */
    private static final String FINALIZE_METHOD = "finalize";
    /**
     * 如果实现了writeReplace方法后，那么在序列化时会先调用writeReplace方法将当前对象替换成另一个对象并将其写入流中
     */
    private static final String WRITE_REPLACE_METHOD = "writeReplace";

    public CglibProxyFactory() {
        try {
            // 校验是否存在Cglib的包
            Resources.classForName("net.sf.cglib.proxy.Enhancer");
        } catch (Throwable e) {
            throw new IllegalStateException("Cannot enable lazy loading because CGLIB is not available. Add CGLIB to your classpath.", e);
        }
    }

    /**
     * 创建指定对象的代理类
     *
     * @param target              指定对象(被代理的对象)
     * @param lazyLoader          懒加载的属性集合
     * @param configuration       Mybatis全局配置
     * @param objectFactory       对象创建工厂
     * @param constructorArgTypes 被代理对象的构造参数类型集合
     * @param constructorArgs     被代理对象的构造参数集合
     * @return 指定对象的代理类
     */
    @Override
    public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        // 创建指定对象的代理类
        return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
    }

    public Object createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
    }

    @Override
    public void setProperties(Properties properties) {
        // Not Implemented
    }

    static Object crateProxy(Class<?> type, Callback callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(callback);
        enhancer.setSuperclass(type);
        try {
            type.getDeclaredMethod(WRITE_REPLACE_METHOD);
            // ObjectOutputStream will call writeReplace of objects returned by writeReplace
            if (LogHolder.log.isDebugEnabled()) {
                LogHolder.log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");
            }
        } catch (NoSuchMethodException e) {
            enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class});
        } catch (SecurityException e) {
            // nothing to do here
        }
        Object enhanced;
        if (constructorArgTypes.isEmpty()) {
            enhanced = enhancer.create();
        } else {
            Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
            Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
            enhanced = enhancer.create(typesArray, valuesArray);
        }
        return enhanced;
    }

    private static class EnhancedResultObjectProxyImpl implements MethodInterceptor {
        /**
         * 被代理对象的类型
         */
        private final Class<?> type;
        /**
         * 懒加载的数据集合
         */
        private final ResultLoaderMap lazyLoader;
        /**
         * 是否调用任意方法都加载所有的懒加载属性
         */
        private final boolean aggressive;
        /**
         * 触发[加载懒加载]的方法
         */
        private final Set<String> lazyLoadTriggerMethods;
        /**
         * 对象创建工厂
         */
        private final ObjectFactory objectFactory;
        /**
         * 被代理对象的构造参数类型集合
         */
        private final List<Class<?>> constructorArgTypes;
        /**
         * 被代理对象的构造参数集合
         */
        private final List<Object> constructorArgs;

        private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
            this.type = type;
            this.lazyLoader = lazyLoader;
            this.aggressive = configuration.isAggressiveLazyLoading();
            this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
            this.objectFactory = objectFactory;
            this.constructorArgTypes = constructorArgTypes;
            this.constructorArgs = constructorArgs;
        }

        /**
         * 为指定对象创建代理对象
         *
         * @param target              被代理的对象
         * @param lazyLoader          需要懒加载的数据集合
         * @param configuration       Mybatis的全局配置对象
         * @param objectFactory       Mybatis使用的对象创建工厂
         * @param constructorArgTypes 被代理的构造参数类型集合
         * @param constructorArgs     被代理对象的构造参数集合
         * @return 指定对象的代理对象
         */
        public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
            // 获取被代理对象的类型
            final Class<?> type = target.getClass();
            // 创建方法处理器
            EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
            // 创建被代理对象的代理对象
            Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
            // copy属性
            PropertyCopier.copyBeanProperties(type, target, enhanced);
            // 返回被代理的对象
            return enhanced;
        }

        /**
         * 方法拦截器
         *
         * @param enhanced    被代理的对象
         * @param method      当前被拦截的方法
         * @param args        被拦截方法的入参
         * @param methodProxy 方法代理对象
         * @return 原始方法的返回结果
         * @throws Throwable 异常
         */
        @Override
        public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            // 获取当前正在执行方法的名称
            final String methodName = method.getName();
            try {
                // 根据lazyLoader属性限制懒加载的多线程安全问题
                synchronized (lazyLoader) {
                    if (WRITE_REPLACE_METHOD.equals(methodName)) {
                        // 如果调用的是writeReplace，生成被代理类型的对象实例，并copy其属性
                        Object original;
                        if (constructorArgTypes.isEmpty()) {
                            original = objectFactory.create(type);
                        } else {
                            original = objectFactory.create(type, constructorArgTypes, constructorArgs);
                        }
                        PropertyCopier.copyBeanProperties(type, enhanced, original);
                        if (lazyLoader.size() > 0) {
                            // 如果有需要懒加载的对象，创建其序列化代理对象
                            return new CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
                        } else {
                            // 返回原始代理对象
                            return original;
                        }
                    } else {
                        if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
                            // 有懒加载属性,且该方法不是java垃圾回收调用的释放资源方法。
                            if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                                // 如果mybatis开启了任意方法都出发懒加载的功能，或者该方法是触发[加载懒加载属性]的方法
                                // 加载所有未加载属性
                                lazyLoader.loadAll();
                            } else if (PropertyNamer.isSetter(methodName)) {
                                // setter方法
                                final String property = PropertyNamer.methodToProperty(methodName);
                                // 移除懒加载配置
                                lazyLoader.remove(property);
                            } else if (PropertyNamer.isGetter(methodName)) {
                                // getter方法
                                final String property = PropertyNamer.methodToProperty(methodName);
                                if (lazyLoader.hasLoader(property)) {
                                    // 该属性尚未被加载，执行加载操作
                                    lazyLoader.load(property);
                                }
                            }
                        }
                    }
                }
                // 执行代理操作
                return methodProxy.invokeSuper(enhanced, args);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
        }
    }

    /**
     * 反序列化代理对象实例
     */
    private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodInterceptor {
        /**
         * 创建反序列化代理对象实例，注意该方法是一个私有方法
         * @param type 被代理对象的类型
         * @param unloadedProperties 未加载的属性集合
         * @param objectFactory Mybatis对象创建工厂
         * @param constructorArgTypes 被代理对象的构造参数类型集合
         * @param constructorArgs 被代理对象的构造参数集合
         */
        private EnhancedDeserializationProxyImpl(Class<?> type, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
                                                 List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
            super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
        }

        /**
         * 创建指定对象的代理对象
         * @param target 被代理对象的类型
         * @param unloadedProperties 未加载的属性集合
         * @param objectFactory Mybatis的对象创建工厂
         * @param constructorArgTypes 被代理对象的构造参数类型集合
         * @param constructorArgs 被代理对象的构造参数集合
         * @return 指定对象的代理对象
         */
        public static Object createProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
                                         List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
            // 获取被代理对象的类型
            final Class<?> type = target.getClass();
            // 生成反序列化对象的方法拦截处理器
            EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
            // 创建被代理对象的代理对象
            Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
            // copy属性
            PropertyCopier.copyBeanProperties(type, target, enhanced);
            // 返回代理对象
            return enhanced;
        }

        /**
         * 方法拦截器
         * @param enhanced 被代理对象
         * @param method 原始方法
         * @param args 方法入参
         * @param methodProxy 代理方法
         * @return 原方法执行结果
         * @throws Throwable 异常
         */
        @Override
        public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            // 获取方法返回参数
            final Object o = super.invoke(enhanced, method, args);
            return o instanceof AbstractSerialStateHolder ? o : methodProxy.invokeSuper(o, args);
        }

        @Override
        protected AbstractSerialStateHolder newSerialStateHolder(Object userBean, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
                                                                 List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
            return new CglibSerialStateHolder(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
        }
    }

    private static class LogHolder {
        private static final Log log = LogFactory.getLog(CglibProxyFactory.class);
    }

}
